# MusicScreen - This screen allows user to select music he/she wants to listen
# Copyright (C) 2007 Lauri Taimila
# 
# Entertainer is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# Entertainer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import os
import gtk
import time
import pango
import gobject
import clutter

from frontend.user_event import UserEvent
from frontend.gui.screens.screen import Screen
from frontend.gui.transitions.transition import Transition

# Widgets
from frontend.gui.widgets.tabs_menu import TabsMenu
from frontend.gui.widgets.text_menu import TextMenu
from frontend.gui.widgets.image_menu import ImageMenu
from frontend.gui.widgets.text_menu_item import TextMenuItem
from frontend.gui.widgets.image_menu_item import ImageMenuItem
from frontend.gui.widgets.loading_animation import LoadingAnimation
from frontend.gui.widgets.justified_text_menu_item import JustifiedTextMenuItem

from frontend.gui.tabs.tab import Tab
from frontend.gui.widgets.tab_group import TabGroup
from frontend.gui.widgets.list_indicator import ListIndicator

# Music library
from frontend.medialibrary.playlist import Playlist
from frontend.medialibrary.music import MusicLibrary
from frontend.medialibrary.music import Track
from frontend.medialibrary.music import Album

class MusicScreen(Screen):
    """
    MusicScreen
    
    Screen that allows user to browse music library content.
    """
    
    def __init__(self, ui, frontend):
        """
        Initialize screen
        
        @param ui: UserInterface object
        @param frontend: FrontendClient object
        """
        self.group = clutter.Group()
        Screen.__init__(self, ui.STAGE_WIDTH, ui.STAGE_HEIGHT)
        
        self.ui = ui
        self.frontend = frontend
        self.player = ui.get_media_player()
        self.theme = frontend.getTheme()
        self.library = self.frontend.get_music_library()
        
        # Screen Title (Displayed at the bottom left corner)
        screen_title = clutter.Label()
        screen_title.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.13)) + "px")
        screen_title.set_color(self.theme.getColor("screentitle"))
        screen_title.set_text("Music")
        screen_title.set_name("screen_title")
        screen_title.set_position(self.ui.x(0), self.ui.y(0.87))
        screen_title.show()
        self.group.add(screen_title)
        
        # Tabs
        self.tab_group = TabGroup(0.95, 0.13, self.theme.getColor("title"))
        tab1 = self._create_artists_tab()
        tab2 = self._create_albums_tab()
        tab3 = self._create_playlists_tab()
        self.tab_group.add_tab(tab1)
        self.tab_group.add_tab(tab2)
        self.tab_group.add_tab(tab3)
        self.tab_group.set_y(self.ui.y(0.1))
        self.tab_group.set_anchor_point_from_gravity(clutter.GRAVITY_CENTER)
        self.tab_group.set_x(self.ui.x(0.5))
        self.tab_group.show()
        self.tab_group.set_active(True)
        self.group.add(self.tab_group)
        
        self.group.add(tab1)
        self.group.add(tab2)
        self.group.add(tab3)

        self.group.show()
        ui.getStage().add(self.group)
    
    def _async_artist_menu_build(self, artists):
        """
        This function is called as async callback. It is also recursive
        function. This allows us load menu items in the background without
        blocking the user interface.
        """
        artist_menu = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_menu")
        artist_throbber = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_throbber")
        
        # Check if we should stop recursive calls (menu is complete)
        if len(artists) > 0:
            artist = artists[0]
            item = JustifiedTextMenuItem(self.ui.x(0.293), self.ui.y(0.078), self.theme, artist)
            item.set_userdata(artist)
            artist_menu.add_actor(item)
            # Recursive call, remove first artist from the list
            gobject.timeout_add(15, self._async_artist_menu_build, artists[1:])
            return False
        else:
            if self.frontend.get_configuration().show_effects():
                self.timeline = clutter.Timeline(35, 26)
                self.alpha = clutter.Alpha(self.timeline, clutter.smoothstep_inc_func)
                self.behaviour = clutter.BehaviourOpacity(255, 0, self.alpha)
                self.behaviour.apply(artist_throbber)
                self.timeline.start()
            else:
                artist_throbber.hide()
            return False
                
    def _create_artists_tab(self):
        """
        Create a list of artists
        """
        artists_tab = Tab("Artists")
        artists_tab.set_name("artists_tab")
        
        artist_menu = TextMenu(self.theme, self.frontend.get_configuration().show_effects())
        artist_menu.set_name("artist_menu")
        artist_menu.set_item_size(self.ui.x(0.293), self.ui.y(0.078))
        cursor = clutter.Rectangle()
        cursor.set_color((0,0,0,0))
        artist_menu.set_cursor(cursor)
        artist_menu.set_orientation(TextMenu.HORIZONTAL)
        artist_menu.set_row_count(7)
        artist_menu.set_visible_column_count(3)
        artist_menu.set_position(self.ui.x(0.057), self.ui.y(0.208))
        artist_menu.set_active(False)
        artist_menu.show()        
        if self.frontend.get_configuration().show_effects():
            artist_menu.set_animate(True)
        artists_tab.add(artist_menu)
        
        # Display throbber animation while loading artist list
        artist_throbber = LoadingAnimation(40, clutter.Color(255,255,255,255), clutter.Color(128,128,128,128))
        artist_throbber.set_name("artist_throbber")
        artist_throbber.set_position(self.ui.x(0.9), self.ui.y(0.9))
        artist_throbber.show()
        artists_tab.add(artist_throbber)
        
        # Create artist label
        artist_title = clutter.Label()
        artist_title.set_name("artist_title")
        artist_title.set_text(" ")
        artist_title.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0416)) + "px")
        artist_title.set_color(self.theme.getColor("title"))
        artist_title.set_line_wrap(False)
        artist_title.set_ellipsize(pango.ELLIPSIZE_END)
        artist_title.set_position(self.ui.x(0.22), self.ui.y(0.794))
        artist_title.set_size(self.ui.x(0.366), self.ui.y(0.052))
        artist_title.show()
        artists_tab.add(artist_title)
        
        artist_albums = clutter.Label()
        artist_albums.set_name("artist_albums")
        artist_albums.set_text(" ")
        artist_albums.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0365)) + "px")
        artist_albums.set_color(self.theme.getColor("subtitle"))
        artist_albums.set_line_wrap(False)
        artist_albums.set_ellipsize(pango.ELLIPSIZE_END)
        artist_albums.set_position(self.ui.x(0.22), self.ui.y(0.86))
        artist_albums.set_size(self.ui.x(0.366), self.ui.y(0.052))
        artist_albums.show()
        artists_tab.add(artist_albums)
        
        artist_tracks = clutter.Label()
        artist_tracks.set_name("artist_tracks")
        artist_tracks.set_text(" ")
        artist_tracks.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0365)) + "px")
        artist_tracks.set_color(self.theme.getColor("subtitle"))
        artist_tracks.set_position(self.ui.x(0.22), self.ui.y(0.911))
        artist_tracks.set_size(self.ui.x(0.366), self.ui.y(0.052))
        artist_tracks.set_line_wrap(False)
        artist_tracks.set_ellipsize(pango.ELLIPSIZE_END)
        artist_tracks.show()
        artists_tab.add(artist_tracks)
        
        # Create artis menu list indicator
        artist_li = ListIndicator(self.ui.x(0.146), self.ui.y(0.044), self.theme, ListIndicator.HORIZONTAL)
        artist_li.set_name("list_indicator")
        artist_li.set_position(self.ui.x(0.929) - artist_li.get_width(), self.ui.y(0.794))
        artist_li.set_delimiter(" | ")
        artist_li.set_maximum(len(self.library.get_all_artists()))
        artist_li.set_current(1)
        artist_li.show()
        artists_tab.add(artist_li)
        
        gobject.timeout_add(25, self._async_artist_menu_build, self.library.get_all_artists())
        
        return artists_tab

    def _async_album_menu_build(self, albums):
        """
        This function is called as async callback. It is also recursive
        function. This allows us load menu items in the background without
        blocking the user interface.
        """
        album_menu = self.tab_group.get_tab_by_index(1).find_child_by_name("album_menu")
        album_throbber = self.tab_group.get_tab_by_index(1).find_child_by_name("album_throbber")
        
        # Check if we should stop recursive calls (menu is complete)
        if len(albums) > 0:
            album = albums[0]
            if album.has_album_art():
                texture = clutter.Texture()
                buffer = gtk.gdk.pixbuf_new_from_file(album.get_album_art_url())
                texture.set_pixbuf(buffer)
            else:
                texture = clutter.CloneTexture(self.default_cover)
            
            item = ImageMenuItem(self.ui.x(0.1098), self.ui.y(0.1953125), texture)
            item.set_userdata(album)
            album_menu.add_actor(item)
            # Recursive call, remove first album from the list
            gobject.timeout_add(25, self._async_album_menu_build, albums[1:])
            return False
        else:
            if self.frontend.get_configuration().show_effects():
                self.timeline_album_throbber = clutter.Timeline(35, 26)
                self.alpha_album_throbber = clutter.Alpha(self.timeline_album_throbber, clutter.smoothstep_inc_func)
                self.behaviour_album_throbber = clutter.BehaviourOpacity(255, 0, self.alpha_album_throbber)
                self.behaviour_album_throbber.apply(album_throbber)
                self.timeline_album_throbber.start()
            else:
                album_throbber.hide()
            return False
               
    def _create_albums_tab(self):
        """
        Create a list of albums
        """
        albums_tab = Tab("Albums")
        albums_tab.set_name("albums_tab")
        
        # Create albums menu
        album_menu = ImageMenu(self.theme)
        album_menu.set_name("album_menu")
        album_menu.set_item_size(self.ui.x(0.1098), self.ui.y(0.1953125))
        album_menu.set_position(self.ui.x(0.0586), self.ui.y(0.1822))
        album_menu.set_active(False)
        album_menu.show()
        
        # Load default albumart texture
        self.default_cover = clutter.Texture()
        buffer = gtk.gdk.pixbuf_new_from_file(self.theme.getImage("default_album_art"))
        self.default_cover.set_pixbuf(buffer)
        
        if self.frontend.get_configuration().show_effects():
            album_menu.set_animate(True)
        
        albums_tab.add(album_menu)
        
        # Display throbber animation while loading artist list
        album_throbber = LoadingAnimation(40, clutter.Color(255,255,255,255), clutter.Color(128,128,128,128))
        album_throbber.set_name("album_throbber")
        album_throbber.set_position(self.ui.x(0.9), self.ui.y(0.9))
        album_throbber.show()
        albums_tab.add(album_throbber)
        
        # Create album menu list indicator
        album_li = ListIndicator(self.ui.x(0.1464), self.ui.y(0.0442), self.theme, ListIndicator.HORIZONTAL)
        album_li.set_name("list_indicator")
        album_li.set_position(self.ui.x(0.9297) - album_li.get_width(), self.ui.y(0.794))
        album_li.set_delimiter(" | ")
        album_li.set_maximum(len(self.library.get_all_albums()))
        album_li.set_current(1)
        album_li.show()
        albums_tab.add(album_li)
        
        # Begin menu load in the background
        gobject.timeout_add(50, self._async_album_menu_build, self.library.get_all_albums()) 
        
        # Create album information (displays current menu item information)
        album_title = clutter.Label()
        album_title.set_name("album_title")
        album_title.set_text(" ")
        album_title.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0416)) + "px")
        album_title.set_color(self.theme.getColor("title"))
        album_title.set_line_wrap(False)
        album_title.set_ellipsize(pango.ELLIPSIZE_END)
        album_title.set_position(self.ui.x(0.219), self.ui.y(0.794))
        album_title.set_size(self.ui.x(0.366), self.ui.y(0.0521))
        album_title.show()
        albums_tab.add(album_title)
        
        album_artist = clutter.Label()
        album_artist.set_name("album_artist")
        album_artist.set_text(" ")
        album_artist.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0365)) + "px")
        album_artist.set_color(self.theme.getColor("subtitle"))
        album_artist.set_line_wrap(False)
        album_artist.set_ellipsize(pango.ELLIPSIZE_END)
        album_artist.set_position(self.ui.x(0.219), self.ui.y(0.859375))
        album_artist.set_size(self.ui.x(0.366), self.ui.y(0.0521))
        album_artist.show()
        albums_tab.add(album_artist)
        
        album_tracks = clutter.Label()
        album_tracks.set_name("album_tracks")
        album_tracks.set_text(" ")
        album_tracks.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.0365)) + "px")
        album_tracks.set_color(self.theme.getColor("subtitle"))
        album_tracks.set_position(self.ui.x(0.219), self.ui.y(0.9115))
        album_tracks.set_size(self.ui.x(0.366), self.ui.y(0.0521))
        album_tracks.set_line_wrap(False)
        album_tracks.set_ellipsize(pango.ELLIPSIZE_END)
        album_tracks.show()
        albums_tab.add(album_tracks)
        
        return albums_tab
        
    def _create_playlists_tab(self):
        """
        Create a list of playlists
        """
        playlists_tab = Tab("Playlists")
        playlists_tab.set_name("playlists_tab")
        
        playlists = self.library.get_playlists()
        
        if len(playlists) == 0:
            empty_label = clutter.Label()
            empty_label.set_name("empty_label")
            empty_label.set_text("No saved playlists available.")
            empty_label.set_font_name(self.theme.getFont() + " " + str(self.ui.y(0.05)) + "px")
            empty_label.set_color(self.theme.getColor("title"))
            empty_label.set_anchor_point_from_gravity(clutter.GRAVITY_CENTER)
            empty_label.set_position(self.ui.x(0.5), self.ui.y(0.5))
            empty_label.show()
            playlists_tab.add(empty_label)
        else:   
            playlist_menu = TextMenu(self.theme, self.frontend.get_configuration().show_effects())
            playlist_menu.set_name("playlist_menu")
            playlist_menu.set_item_size(self.ui.x(0.9115), self.ui.y(0.07813))
            
            for playlist in playlists:
                item = TextMenuItem(self.ui.x(0.9115), self.ui.y(0.07813), self.theme, playlist)
                item.set_userdata(playlist)
                playlist_menu.add_actor(item)
            
            playlist_menu.set_position(self.ui.x(0.4026), self.ui.y(0.1823))
            playlist_menu.show()
            
            if self.frontend.get_configuration().show_effects():
                playlist_menu.set_animate(True)
            
            playlists_tab.add(playlist_menu)
            
        return playlists_tab
    
    def _update_album_info(self, album):
        """
        Update album information. This information is displayed on albums tab.
        @param album: Album object
        """
        album_title = self.tab_group.get_tab_by_index(1).find_child_by_name("album_title")
        album_artist = self.tab_group.get_tab_by_index(1).find_child_by_name("album_artist")
        album_tracks = self.tab_group.get_tab_by_index(1).find_child_by_name("album_tracks")
        album_title.set_text(album.get_title())
        album_artist.set_text(album.get_artist())
        album_tracks.set_text(str(album.get_number_of_tracks()) + " tracks")
       
    def _update_artist_info(self, artist):
        """
        Update artist information. This information is displayed on artist tab.
        @param artist: Artist name
        """
        artist_title = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_title")
        artist_albums = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_albums")
        artist_tracks = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_tracks")
        artist_title.set_text(artist)
        artist_albums.set_text(str(self.library.number_of_albums_by_artist(artist)) + " albums")
        artist_tracks.set_text(str(self.library.number_of_tracks_by_artist(artist)) + " tracks")
       
    def get_group(self):
        """
        Get all actors of the screen.
        @return clutter.Group 
        """
        return self.group

    def get_type(self):
        """
        Get screen type.
        @return: Integer. Constant defined in Screen class
        """
        return Screen.NORMAL
    
    def get_name(self):
        """
        Get screen name (human readable)
        @return: String
        """
        return "MusicScreen"
    
    def is_interested_in_play_action(self):
        """
        Override function from Screen class. See Screen class for 
        better documentation.
        @return: Boolean
        """
        artist_menu = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_menu")
        album_menu = self.tab_group.get_tab_by_index(1).find_child_by_name("album_menu")
        
        if artist_menu.is_active() or album_menu.is_active():
            return True
        else:
            return False
    
    def execute_play_action(self):
        """
        Override function from Screen class. See Screen class for 
        better documentation.
        """
        artist_menu = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_menu")
        album_menu = self.tab_group.get_tab_by_index(1).find_child_by_name("album_menu")
        
        if artist_menu.is_active():
            artist = self.artist_menu.get_current_menuitem().get_userdata()
            self.player.set_playlist(Playlist(self.library.get_tracks_by_artist(artist)))
            self.player.play()
        elif album_menu.is_active():
            album = self.album_menu.get_current_menuitem().get_userdata()
            self.player.set_playlist(Playlist(album.get_tracks()))
            self.player.play()
    
    def handle_user_event(self, event):   
        """ 
        Handle screen specific user events
        @param event: UserEvent object
        """
        type = event.get_type()
        
        tab = self.tab_group.get_current_tab()
        artist_menu = self.tab_group.get_tab_by_index(0).find_child_by_name("artist_menu")
        artist_li = self.tab_group.get_tab_by_index(0).find_child_by_name("list_indicator")
        album_menu = self.tab_group.get_tab_by_index(1).find_child_by_name("album_menu")
        album_li = self.tab_group.get_tab_by_index(1).find_child_by_name("list_indicator")
        
        if type == UserEvent.NAVIGATE_UP:
            if not self.tab_group.is_active():
                if tab.get_name() == "artists_tab": 
                    if artist_menu.get_cursor_position()[1] == 0:
                        artist_menu.set_active(False)
                        self.tab_group.set_active(True)
                    else:
                        artist_menu.move(artist_menu.UP)
                        self._update_artist_info(artist_menu.get_current_menuitem().get_userdata())
                        artist_li.set_current(artist_menu.get_current_postition() + 1)
                
                elif tab.get_name() == "albums_tab":
                    if album_menu.get_cursor_position()[1] == 0:
                        album_menu.set_active(False)
                        self.tab_group.set_active(True)
                    else:
                        album_menu.move(album_menu.UP)
                        self._update_album_info(album_menu.get_current_menuitem().get_userdata())
                        album_li.set_current(album_menu.get_current_postition() + 1)
                        
                elif tab.get_name() == "playlists_tab":
                    pass

        
        elif type == UserEvent.NAVIGATE_DOWN:
            if self.tab_group.is_active():
                if tab.get_name() == "artists_tab":
                    artist_menu.set_active(True)
                    self.tab_group.set_active(False)
                    self._update_artist_info(artist_menu.get_current_menuitem().get_userdata())
                elif tab.get_name() == "albums_tab":
                    album_menu.set_active(True)
                    self.tab_group.set_active(False)
                    self._update_album_info(album_menu.get_current_menuitem().get_userdata())
                elif tab.get_name() == "playlists_tab":
                    try:
                        playlist_menu.set_active(True)
                        self.tab_group.set_active(False)
                    except:
                        pass
                return
            else:
                if tab.get_name() == "artists_tab":
                    artist_menu.move(artist_menu.DOWN)
                    self._update_artist_info(artist_menu.get_current_menuitem().get_userdata())
                    artist_li.set_current(artist_menu.get_current_postition() + 1)
                elif tab.get_name() == "albums_tab":
                    album_menu.move(album_menu.DOWN)
                    self._update_album_info(album_menu.get_current_menuitem().get_userdata())
                    album_li.set_current(album_menu.get_current_postition() + 1)
                elif tab.get_name() == "playlists_tab":
                    pass
        
        elif type == UserEvent.NAVIGATE_LEFT:
            if self.tab_group.is_active():
                self.tab_group.handle_user_event(event)
            else:
                if tab.get_name() == "artists_tab":
                    artist_menu.move(artist_menu.LEFT)
                    self._update_artist_info(artist_menu.get_current_menuitem().get_userdata())
                    artist_li.set_current(artist_menu.get_current_postition() + 1)
                elif tab.get_name() == "albums_tab":
                    album_menu.move(album_menu.LEFT)
                    self._update_album_info(album_menu.get_current_menuitem().get_userdata())
                    album_li.set_current(album_menu.get_current_postition() + 1)
                elif tab.get_name() == "playlists_tab":
                    pass
                
        elif type == UserEvent.NAVIGATE_RIGHT:
            if self.tab_group.is_active():
                self.tab_group.handle_user_event(event)
            else:
                if tab.get_name() == "artists_tab":
                    artist_menu.move(artist_menu.RIGHT)
                    self._update_artist_info(artist_menu.get_current_menuitem().get_userdata())
                    artist_li.set_current(artist_menu.get_current_postition() + 1)
                elif tab.get_name() == "albums_tab":
                    album_menu.move(album_menu.RIGHT)
                    self._update_album_info(album_menu.get_current_menuitem().get_userdata())
                    album_li.set_current(album_menu.get_current_postition() + 1)
                elif tab.get_name() == "playlists_tab":
                    pass

        
        elif type == UserEvent.NAVIGATE_SELECT:
            if not self.tab_group.is_active():                
                if tab.get_name() == "artists_tab": 
                    artist = artist_menu.get_current_menuitem().get_userdata()
                    self.ui.changeScreen(self.ui.createScreen("artist", artist), Transition.FORWARD)
                    
                elif tab.get_name() == "albums_tab": 
                    album = album_menu.get_current_menuitem().get_userdata()
                    self.ui.changeScreen(self.ui.createScreen("music_album", album), Transition.FORWARD)
                    
                elif tab.get_name() == "playlists_tab": 
                    pass
                